//
// Created by Tony on 2024/6/14.
//

#include "InstructionFactory.h"

#include "ADD.h"
#include "DIV.h"
#include "GET_FIELD.h"
#include "GET_STATIC.h"
#include "GT.h"
#include "IF_FALSE.h"
#include "IF_TRUE.h"
#include "INVOKE_SPECIAL.h"
#include "INVOKE_STATIC.h"
#include "INVOKE_VIRTUAL.h"
#include "Instruction.h"
#include "JMP.h"
#include "LOAD_K.h"
#include "LOAD_V.h"
#include "LT.h"
#include "MUL.h"
#include "NEW.h"
#include "PUT_FIELD.h"
#include "PUT_STATIC.h"
#include "RETURN.h"
#include "RETURN_V.h"
#include "STORE_V.h"
#include "SUB.h"
#include <cstdint>
#include <sstream>
#include <stdexcept>
#include <vector>

namespace Runtime {
namespace InstructionFactory {

using Common::OpCode;
using std::runtime_error;
using std::vector;

vector<Instruction*> INSTR_TABLE;

void init() {
    if (!INSTR_TABLE.empty()) {
        return;
    }
    INSTR_TABLE = vector<Instruction*>(256, nullptr);

    INSTR_TABLE[static_cast<uint8_t>(OpCode::LOAD_K)] = new LOAD_K();
    INSTR_TABLE[static_cast<uint8_t>(OpCode::LOAD_V)] = new LOAD_V();
    INSTR_TABLE[static_cast<uint8_t>(OpCode::STORE_V)] = new STORE_V();

    INSTR_TABLE[static_cast<uint8_t>(OpCode::NEW)] = new NEW();

    INSTR_TABLE[static_cast<uint8_t>(OpCode::PUT_FIELD)] = new PUT_FIELD();
    INSTR_TABLE[static_cast<uint8_t>(OpCode::GET_FIELD)] = new GET_FIELD();

    INSTR_TABLE[static_cast<uint8_t>(OpCode::PUT_STATIC)] = new PUT_STATIC();
    INSTR_TABLE[static_cast<uint8_t>(OpCode::GET_STATIC)] = new GET_STATIC();

    INSTR_TABLE[static_cast<uint8_t>(OpCode::INVOKE_STATIC)] = new INVOKE_STATIC();
    INSTR_TABLE[static_cast<uint8_t>(OpCode::INVOKE_SPECIAL)] = new INVOKE_SPECIAL();
    INSTR_TABLE[static_cast<uint8_t>(OpCode::INVOKE_VIRTUAL)] = new INVOKE_VIRTUAL();

    INSTR_TABLE[static_cast<uint8_t>(OpCode::RETURN)] = new RETURN();
    INSTR_TABLE[static_cast<uint8_t>(OpCode::RETURN_V)] = new RETURN_V();

    INSTR_TABLE[static_cast<uint8_t>(OpCode::ADD)] = new ADD();
    INSTR_TABLE[static_cast<uint8_t>(OpCode::SUB)] = new SUB();
    INSTR_TABLE[static_cast<uint8_t>(OpCode::MUL)] = new MUL();
    INSTR_TABLE[static_cast<uint8_t>(OpCode::DIV)] = new DIV();

    INSTR_TABLE[static_cast<uint8_t>(OpCode::LT)] = new LT();
    INSTR_TABLE[static_cast<uint8_t>(OpCode::GT)] = new GT();

    INSTR_TABLE[static_cast<uint8_t>(OpCode::IF_TRUE)] = new IF_TRUE();
    INSTR_TABLE[static_cast<uint8_t>(OpCode::IF_FALSE)] = new IF_FALSE();
    INSTR_TABLE[static_cast<uint8_t>(OpCode::JMP)] = new JMP();
}

Instruction* create(OpCode opcode) {
    auto* instr = INSTR_TABLE[static_cast<uint8_t>(opcode)];
    if (instr == nullptr) {
        std::stringstream ss;
        ss << "not support opcode: " << static_cast<uint16_t>(opcode);
        throw runtime_error(ss.str());
    }
    return instr;
}

} // namespace InstructionFactory
} // namespace Runtime